﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using TDB;
using TDB.CustomExceptions;
using TDB.DBInternals.Struct;
using TDB.Services;

namespace TDB.DBInternals
{
  public interface IStore
  { }

  public class ObjectStore<T> : IStore
  {
    public string Name { get; private set; }

    #region Private Fields and function for storeid
    private long _storelonguid = 0;
    private int _storeintuid = 0;
    private StoreGuid _storeGuid = new StoreGuid();

    private Func<object> newIdFunction;
    private Func<object> currentIdFunction;
    #endregion

    private Dictionary<string, PropertyInfo> _properties = new Dictionary<string, PropertyInfo>();
    private Journaling _journal = new Journaling();

    #region Private fields
    internal PropertyInfo _primarykey_property = null;
    internal volatile Dictionary<dynamic, T> _primarystore = new Dictionary<dynamic, T>();
    #endregion

    #region Constructor
    internal ObjectStore(string name)
    {
      Name = name;
      _journal.DataBaseName = name;
      _journal.Init();

      var objtype = typeof(T);
      var attr = objtype.GetCustomAttributes(typeof(EntityUniqueIdentifierAttribute), true).FirstOrDefault() as EntityUniqueIdentifierAttribute;

      if (attr == null)
        throw new InvalidEntityDeclarationException(objtype, "EntityUniqueIdentifier Attribute is missing in class declaration");

      _primarykey_property = objtype.GetProperty(attr.UniqueIdentifier, BindingFlags.Public | BindingFlags.Instance);
      if (_primarykey_property == null)
        throw new InvalidEntityDeclarationException(objtype, string.Format("The entity does not has a public property named {0}", attr.UniqueIdentifier));

      var ptype = _primarykey_property.PropertyType;
      if (ptype != typeof(long) && ptype != typeof(int) && ptype != typeof(Guid))
        throw new InvalidEntityDeclarationException(objtype, string.Format("The property named {0} must be a long(int64), integer(Int32) or Guid", attr.UniqueIdentifier));

      if (ptype == typeof(int))
      {
        newIdFunction = () => ++_storeintuid;
        currentIdFunction = () => _storeintuid;
      }

      if (ptype == typeof(long))
      {
        newIdFunction = () => ++_storelonguid;
        currentIdFunction = () => _storelonguid;
      }

      if (ptype == typeof(Guid))
      {
        _storeGuid.init();
        newIdFunction = () => _storeGuid.Increment();
        currentIdFunction = () => _storeGuid.GetUid();
      }

      var props = objtype.GetProperties(BindingFlags.Instance | BindingFlags.Public);
      if (props != null)
        foreach (var pi in props)
          _properties.Add(pi.Name, pi);
    }
    #endregion

    #region Method for storing objects
    public void Set(T entity)
    {
      var lastid = currentIdFunction();
      var uid = GetEntityUI(entity);
      if (uid.CompareTo(lastid) <= 0)
        if (_primarystore.ContainsKey(uid))
        {
          _primarystore[uid] = entity;
          return;
        }
      _primarystore.Add(uid, entity);
    }
    #endregion

    #region private methods
    private dynamic GetEntityUI(T entity)
    {
      dynamic uivalue = _primarykey_property.GetValue(entity, null);
      if (uivalue == 0)
      {
        uivalue = newIdFunction();
        _primarykey_property.SetValue(entity, uivalue, null);
      }
      return uivalue;
    }

    #endregion

    #region Querymethods

    public IEnumerable<T> Where(Func<T, bool> filter)
    {
      if (filter != null)
        return _primarystore.Values.Where(filter).AsParallel().AsEnumerable();
      return null;
    }
    #endregion
  }
}
